java并发系列 |
您所在的位置:网站首页 › synchronized monitor锁 › java并发系列 |
背景
在jdk1.6以前synchronized的java内置锁不存在 偏向锁->轻量级锁->重量级锁 的锁膨胀机制,锁膨胀机制是1.6之后为了优化java线程同步性能而实现的。而1.6之前都是基于monitor机制的重量级锁。因为java内部对锁实现的封装,就算现在我们也只需要了解重量级锁就可以了。深入了解monitor机制对学习线程同步非常重要。 正文 目录 什么是monitor monitor的作用 monitor的组成 寻找monitor锁 java monitor机制的实现什么是monitor 参考 monitor直译过来是监视器的意思,专业一点叫管程。monitor是属于编程语言级别的,它的出现是为了解决操作系统级别关于线程同步原语的使用复杂性,类似于语法糖,对复杂操作进行封装。而java则基于monitor机制实现了它自己的线程同步机制,就是synchronized内置锁。 monitor的作用 monitor的作用就是限制同一时刻,只有一个线程能进入monitor框定的临界区,达到线程互斥,保护临界区中临界资源的安全,这称为线程同步使得程序线程安全。同时作为同步工具,它也提供了管理进程,线程状态的机制,比如monitor能管理因为线程竞争未能第一时间进入临界区的其他线程,并提供适时唤醒的功能。 monitor的组成 3.1 monitor对象 monitor对象是monitor机制的核心,它本质上是jvm用c语言定义的一个数据类型。对应的数据结构保存了线程同步所需的信息,比如保存了被阻塞的线程的列表,还维护了一个基于mutex的锁,monitor的线程互斥就是通过mutex互斥锁实现的。 3.2 临界区 临界区是被synchronized包裹的代码块,可能是个代码块,也可能是个方法。 3.3 条件变量 条件变量和下方wait signal方法的使用有密切关系 。在获取锁进入临界区之后,如果发现条件变量不满足使用wait方法使线程阻塞,条件变量满足后signal唤醒被阻塞线程。 tips:当线程被signal唤醒之后,不是从wait那继续执行的,而是重新while循环一次判断条件是否成立。参考 3.4 定义在monitor对象上的wait() signal() signalAll()操作 java中monitor的实现4.1 首先先看一下synchronized同步代码块和同步方法编译后的字节码指令文件分别是什么样子 源代码如下 public class SynchronizedTest { public synchronized void test1(){ } public void test2(){ synchronized (this){ } } }
接着我们用javap查看
从上面可以看出,同步方法jvm是使用ACC_SYNCHRONIZED方法访问标识符实现同步,同步代码块jvm是使用monitorenter和monitorexit指令包裹临界区实现同步。
4.2 线程执行到同步方法处和同步代码块monitorenter和monitorexit指令分别发生了什么 这里需要看jvm的官方文档,下面三段话要好好读一读,monitor的运行逻辑都包含在里面。 同步方法 文档 ![]() ![]()
同步代码块指令 文档 monitorenter ![]() ![]() monitorexit ![]() ![]() 对比官方文档描述的同步方法和同步代码块指令,其实功能类似。总结如下 1.同步方法和同步代码块都是通过monitor锁实现的。 2.两者的区别:同步方式是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现;同步代码块是通过monitorenter和monitorexit指令来实现 3.每个java对象都会与一个monitor相关联,可以由线程获取和释放。 4.如果线程没有获取到monitor会被阻塞。 5.monitor通过维护一个计数器来记录锁的获取,重入,释放情况。
由此可知当线程执行到同步方法发现此方法有ACC_SYNCHRONIZED标志或者执行到monitorenter指令时,会去尝试获取monitor锁。 那么就会有个疑问,既然线程需要获取monitor锁,那么什么是monitor锁,并且怎么才算获取monitor锁。
4.3 寻找monitor锁 这里先不甩结论,接下来我们一步一步搜寻monitor锁。 之前使用synchronized的时候知道,java中的每个对象都可以作为锁。 普通同步方法,锁是当前实例对象。 静态同步方法,锁是当前类的class对象。 同步代码块,锁是括号中的对象。上面的官方文档也说了每个对象都与一个监视器关联。有理由猜测,任意的java对象在实例化的时候都同时生成了一个monitor锁与之一一对应。那么进一步猜测,通过java对象可以获取到和它对应的监视器。 这时候涉及到对象头的知识点。 4.3.1 对象头 对象头知识参考1 参考2 |-------------------------------------------------------|--------------------| | Mark Word (32 bits) | State | |-------------------------------------------------------|--------------------| | identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal | |-------------------------------------------------------|--------------------| | thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased | |-------------------------------------------------------|--------------------| | ptr_to_lock_record:30 | lock:2 | Lightweight Locked | |-------------------------------------------------------|--------------------| | ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked | |-------------------------------------------------------|--------------------| | | lock:2 | Marked for GC | |-------------------------------------------------------|--------------------|每个java对象在内存中由对象头,实例数据和对齐填充三块区域组成。其中对象头存储了一些增强对象功能的信息,对象头中的Mark word 记录了锁的相关信息。如果此刻该对象锁升级为重量级锁,那么其中在对象头中存储了指向基于monitor锁的指针ptr_to_heavyweight_monitor。这个指针指向的就是我们苦苦寻找的锁。 既然监视器是指针指向的内存区域,那么这块内存区域肯定有自己的数据结构,而这个数据结构保存着线程同步的所有信息。
4.3.2 揭开monitor锁神秘面纱 详情参考 monitor的定义和初始化是有c语言编写的。 http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/095e60e7fc8c/src/share/vm/runtime/objectMonitor.hpp
最重要的就是这两个c语言定义的类,objectMonitor就是对象头中指向的monitor重量级锁,objectWaiter是对等待线程的封装,可以用双向链表保存起来。 下面解释objectMonitor中属性的含义 _header 定义: volatile markOop _header; // displaced object header word - mark说明: _header是一个markOop类型,markOop就是对象头中的Mark Word _count 定义: volatile intptr_t _count; // reference count to prevent reclaimation/deflation // at stop-the-world time. See deflate_idle_monitors(). // _count is approximately |_WaitSet| + |_EntryList|说明:抢占该锁的线程数 约等于 WaitSet.size + EntryList.size _waiters定义: volatile intptr_t _waiters; // number of waiting threads说明:等待线程数 _recursions定义: volatile intptr_t _recursions; // recursion count, 0 for first entry说明:锁重入次数 _object定义: void* volatile _object; // backward object pointer - strong root说明:监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中 _owner定义: void * volatile _owner; // pointer to owning thread OR BasicLock说明: 指向获得ObjectMonitor对象的线程或基础锁 _WaitSet定义: ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor说明:处于wait状态的线程,被加入到这个linkedList _WaitSetLock定义: volatile int _WaitSetLock; // protects Wait Queue - simple spinlock说明:protects Wait Queue - simple spinlock ,保护WaitSet的一个自旋锁(monitor大锁里面的一个小锁,这个小锁用来保护_WaitSet更改) _Responsible定义: Thread * volatile _Responsible说明:未知 参考:https://www.jianshu.com/p/09de11d71ef8 _succ定义: Thread * volatile _succ ; // Heir presumptive thread - used for futile wakeup throttling说明:当锁被前一个线程释放,会指定一个假定继承者线程,但是它不一定最终获得锁。参考:https://www.jianshu.com/p/09de11d71ef8 _cxq定义: ObjectWaiter * volatile _cxq ; // LL of recently-arrived threads blocked on entry. // The list is actually composed of WaitNodes, acting // as proxies for Threads.说明:ContentionList 参考:https://www.jianshu.com/p/09de11d71ef8 FreeNext定义: ObjectMonitor * FreeNext ; // Free list linkage说明:未知 _EntryList定义: ObjectWaiter * volatile _EntryList ; // Threads blocked on entry or reentry.说明:未获取锁被阻塞或者被wait的线程重新进入被放入entryList中 _SpinFreq定义: volatile int _SpinFreq ; // Spin 1-out-of-N attempts: success rate说明:未知 可能是获取锁的成功率 _SpinClock定义: volatile int _SpinClock ;说明:未知 OwnerIsThread定义: int OwnerIsThread ; // _owner is (Thread *) vs SP/BasicLock说明:当前owner是thread还是BasicLock _previous_owner_tid定义: volatile jlong _previous_owner_tid; // thread id of the previous owner of the monitor说明:当前owner的线程id 其实上面的属性中我们真正需要了解的就几个。下面大概描述一下。 4.3.3 线程的千里追踪 参考
线程访问同步代码,需要获取monitor锁 线程被jvm托管 jvm获取充当临界区锁的java对象 根据java对象对象头中的重量级锁 ptr_to_heavyweight_monitor指针找到objectMonitor 将当前线程包装成一个ObjectWaiter对象 将ObjectWaiter假如_cxq(ContentionList)队列头部 _count++ 如果owner是其他线程说明当前monitor被占据,则当前线程阻塞。如果没有被其他线程占据,则将owner设置为当前线程,将线程从等待队列中删除,count--。 当前线程获取monitor锁,如果条件变量不满足,则将线程放入WaitSet中。当条件满足之后被唤醒,把线程从WaitSet转移到EntrySet中。 当前线程临界区执行完毕 Owner线程会在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交个OnDeck,OnDeck需要重新竞争锁
大概流程就是这样的,但是其中还有很多没有在这篇博客中提及的知识点就不深入了。
|
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |